﻿#region  == Copyright ==
// =====================================================================
//  Microsoft Consulting Services France - Aymeric Mouillé - 2013
//  Projet     : Solution Cmd Helper - Solution Cmd Helper
//  Fichier    : Program.cs (24/06/2013 20:49)
//  
//  Copyright (C) Microsoft Corporation.  All rights reserved.
// =====================================================================
#endregion

namespace Solution.Cmd.Helper
{
    #region  == Using ==
    using System;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Threading;

    using Microsoft.Crm.Sdk.Messages;
    using Microsoft.Xrm.Client;
    using Microsoft.Xrm.Client.Services;
    using Microsoft.Xrm.Sdk;
    using Microsoft.Xrm.Sdk.Query;

    #endregion

    internal class Program
    {
        private static bool HasFailed;

        private static bool IsCurrentImportRunning;

        private static IOrganizationService OrganizationService;

        private static Logger Logger;

        #region Main
        /// <summary>
        /// Launch solution operation
        /// ----
        /// Parameters
        /// - Operation : import, export, delete, publish
        /// - CrmConnectionString : CRM organization url + credential
        /// - ConfirmPrompt : Ask to start processing
        /// - Hide header : don't display the header message
        /// - NoPrompt : Don't ask for retry
        /// Export, Import, Delete parameters :
        /// - SolutionName : CRM Solution Name
        /// - SolutionFilePath : Solution File Path
        /// Specific export parameters :
        /// - Managed
        /// - AutoNumberingSettings
        /// - CalendarSettings
        /// - CustomizationSettings
        /// - EmailTrackingSettings
        /// - GeneralSettings
        /// - IsvConfig
        /// - MarketingSettings
        /// - OutlookSynchronizationSettings
        /// - RelationshipRoles
        /// Specific import parameters :
        /// - PublishWorkflows
        /// - OverwriteUnmanagedCustomizations
        /// - LogFilePath : Result Log Path
        /// </summary>
        /// <param name="args"></param>
        private static int Main(string[] args)
        {
            ConsoleHelper.SetConsoleSize();
            CommandArgs commandArgs = new CommandArgs(args);
            Logger = new Logger(commandArgs.ApplicationLogFilePath);
            Logger.Write("***************************************************************************************************************");
            Logger.Write("Process start");
            if (!commandArgs.HideHeader)
            {
                ConsoleHelper.DisplaySummary("Solution CommandLine Helper " + Version.VersionNumber, "Command line application to manage CRM Solution deployment operations.");
            }
            string validationError;
            if (!commandArgs.IsValid(out validationError))
            {
                Logger.Write(string.Format("Missing arguments {0}", validationError));
                ConsoleHelper.WriteError(string.Format("Invalid command line parameters : {0}", validationError));
                DisplayUsage();
                return 2;
            }
            else
            {
                if (commandArgs.ConfirmPrompt)
                {
                    if (!ConsoleHelper.AskForContinue())
                    {
                        return 3;
                    }
                }
                string argOutput = commandArgs.Output();
                Logger.Write(argOutput);
                ConsoleHelper.WriteLine(argOutput, ConsoleColor.DarkGray);
                Execute(commandArgs);
            }
#if DEBUG
            ConsoleHelper.End();
#endif
            Logger.Write("Process stop");
            Logger.Write("***************************************************************************************************************");

            return (!HasFailed) ? 0 : 1;
        }
        #endregion

        #region Display Usage
        /// <summary>
        /// Displays command line usage.
        /// </summary>
        private static void DisplayUsage()
        {
            ConsoleHelper.WriteFullLine('*', ConsoleColor.DarkGray);
            ConsoleHelper.WriteLine("Parameters                                                                              ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(" /operation:value (import, export, delete, publish)                                     ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(" /crmconnectionstring:value (CRM organization url + credential)                         ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(" /logpath:value (process log file path)                                                 ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(" /confirmprompt (Ask to start processing)                                               ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(" /noprompt (Don't ask to retry or abort  processing)                                    ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(" /hideheader (hide header message)                                                      ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(string.Empty, ConsoleColor.Gray);
            ConsoleHelper.WriteLine("Export, Import, Delete parameters :                                                     ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(" /solutionname:value (CRM Solution Name)                                                ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(" /solutionfilepath:value (Solution File Path)                                           ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(string.Empty, ConsoleColor.Gray);
            ConsoleHelper.WriteLine("Specific export parameters :                                                            ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(" /managed                                                                               ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(" /autoNumberingSettings                                                                 ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(" /calendarsettings                                                                      ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(" /customizationsettings                                                                 ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(" /emailtrackingsettings                                                                 ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(" /generalsettings                                                                       ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(" /isvconfig                                                                             ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(" /marketingsettings                                                                     ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(" /outlooksynchronizationsettings                                                        ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(" /relationshiproles                                                                     ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine("                                                                                        ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine("Specific import parameters :                                                            ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(" /publishworkflows                                                                      ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(" /overwriteunmanagedcustomizations                                                      ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(" /logfilepath:value (Result Log Path)                                                   ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine("                                                                                        ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine("Examples                                                                                ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine("                                                                                        ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(@"Solution.Cmd.Helper.exe /operation:delete /crmconnectionstring:'Url=http://serveur/org; Domain=dom; Username=usr; Password=pass' /solutionname:'My solution' /confirmprompt                                                                              ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(string.Empty, ConsoleColor.Gray);
            ConsoleHelper.WriteLine(
                @"Solution.Cmd.Helper.exe /operation:export /crmconnectionstring:'Url=http://serveur/org; Domain=dom; Username=usr; Password=pass' /solutionname:'My solution' /solutionfilepath:'c:\solution.zip' /managed /generalsettings  /autoNumberingSettings:'c:\error.xml'                                                                              ",
                ConsoleColor.Gray);
            ConsoleHelper.WriteLine(string.Empty, ConsoleColor.Gray);
            ConsoleHelper.WriteLine(@"Solution.Cmd.Helper.exe /operation:import /crmconnectionstring:'Url=http://serveur/org; Domain=dom; Username=usr; Password=pass' /solutionfilepath:'c:\solution.zip' /publishworkflows /logfilepath:'c:\error.xml'                                                                              ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(string.Empty, ConsoleColor.Gray);
            ConsoleHelper.WriteLine(@"Solution.Cmd.Helper.exe /operation:publish /crmconnectionstring:'Url=http://serveur/org; Domain=dom; Username=usr; Password=pass'                                                                              ", ConsoleColor.Gray);
            ConsoleHelper.WriteLine(string.Empty, ConsoleColor.Gray);
            ConsoleHelper.WriteFullLine('*', ConsoleColor.DarkGray);
        }
        #endregion

        #region Execute
        /// <summary>
        /// Executes the specified command.
        /// </summary>
        /// <param name="command">The args.</param>
        internal static void Execute(CommandArgs command)
        {
            Logger.Write("Execution start");
            Stopwatch stopwatch = new Stopwatch();
            try
            {
                InitializeConnection(command.CrmConnectionString);
                stopwatch.Start();
                switch (command.Operation)
                {
                    case OperationTypes.Delete:
                        Delete(command);
                        break;
                    case OperationTypes.Export:
                        Export(command);
                        break;
                    case OperationTypes.Import:
                        Import(command);
                        break;
                    case OperationTypes.Publish:
                        Publish(command);
                        break;
                    default:
                        ConsoleHelper.WriteError("Bad operation name");
                        return;
                }
            }
            catch (Exception exception)
            {
                Logger.Write(string.Format("Error : {0}", exception));
                HasFailed = true;
                ConsoleHelper.WriteError(exception.ToString());
            }
            finally
            {
                stopwatch.Stop();
                Logger.Write(string.Format("Operation duration : {0}", stopwatch.Elapsed));
                Console.WriteLine("Operation completed! (Elapsed time : {0})", stopwatch.Elapsed);
            }

            if (HasFailed)
            {
                if (!command.NoPrompt)
                {
                    if (!ConsoleHelper.AskForRetry())
                    {
                        Logger.Write("Execution stop");
                        return;
                    }
                    Execute(command);
                }
            }
            Logger.Write("Execution stop");
        }
        #endregion

        #region Initialize Connection
        /// <summary>
        /// Initializes the connection.
        /// </summary>
        /// <param name="crmConnectionString">The CRM connection string.</param>
        internal static void InitializeConnection(String crmConnectionString)
        {
            Logger.Write("InitializeConnection start : " + crmConnectionString);
            ConsoleHelper.Write("Connection to CRM ...", ConsoleColor.Gray);
            var connection = CrmConnection.Parse(crmConnectionString);
            connection.Timeout = new TimeSpan(1, 0, 0);
            OrganizationService = new OrganizationService(connection);
            ConsoleHelper.Write("Done !", ConsoleColor.Green);
            Console.WriteLine();
            Logger.Write("InitializeConnection stop");
        }
        #endregion

        #region Delete
        /// <summary>
        /// Deletes the solution with specified command.
        /// </summary>
        /// <param name="command">The command.</param>
        internal static void Delete(CommandArgs command)
        {
            Logger.Write("Delete start : " + command.SolutionName);
            ConsoleHelper.Write("Deleting solution from CRM ...", ConsoleColor.Cyan);
            QueryExpression querySolution = new QueryExpression { EntityName = "solution", ColumnSet = new ColumnSet(new[] { "publisherid", "installedon", "version", "versionnumber", "friendlyname" }), Criteria = new FilterExpression() };
            querySolution.Criteria.AddCondition("uniquename", ConditionOperator.Equal, command.SolutionName);

            var results = OrganizationService.RetrieveMultiple(querySolution);
            if (results.Entities.Count == 1)
            {
                Entity solution = results.Entities[0];
                OrganizationService.Delete("solution", solution.Id);
                ConsoleHelper.Write("Done !", ConsoleColor.Green);
                Console.WriteLine();
                Logger.Write("Delete stop : success!");
                return;
            }

            if (results.Entities.Count <= 0)
            {
                ConsoleHelper.WriteError(string.Format("Unable to find solution with name : {0}", command.SolutionName));
                Logger.Write(string.Format("Delete stop : Unable to find solution with name : {0}", command.SolutionName));
                return;
            }

            if (results.Entities.Count > 1)
            {
                ConsoleHelper.WriteError(string.Format("Request return to many results for solution name : {0}", command.SolutionName));
                ConsoleHelper.WriteError(string.Format("Request return to many results for solution name : {0}", command.SolutionName));
                return;
            }
        }
        #endregion

        #region Export
        /// <summary>
        /// Exports the solution with specified command.
        /// </summary>
        /// <param name="command">The command.</param>
        internal static void Export(CommandArgs command)
        {
            Logger.Write("Export start : " + command.SolutionName);
            ConsoleHelper.Write("Exporting solution from CRM ...", ConsoleColor.Cyan);
            var request = new ExportSolutionRequest
                              {
                                  Managed = command.Managed,
                                  SolutionName = command.SolutionName,
                                  ExportCalendarSettings = command.CalendarSettings,
                                  ExportCustomizationSettings = command.CustomizationSettings,
                                  ExportEmailTrackingSettings = command.EmailTrackingSettings,
                                  ExportAutoNumberingSettings = command.AutoNumberingSettings,
                                  ExportIsvConfig = command.IsvConfig,
                                  ExportOutlookSynchronizationSettings = command.OutlookSynchronizationSettings,
                                  ExportGeneralSettings = command.GeneralSettings,
                                  ExportMarketingSettings = command.MarketingSettings,
                                  ExportRelationshipRoles = command.RelationshipRoles
                              };
            var response = (ExportSolutionResponse)OrganizationService.Execute(request);
            File.WriteAllBytes(command.SolutionFilePath, response.ExportSolutionFile);
            ConsoleHelper.Write("Done !", ConsoleColor.Green);
            Console.WriteLine();
            Logger.Write("Export stop : " + command.SolutionFilePath);
        }
        #endregion

        #region Import
        /// <summary>
        /// Imports the solution with specified command.
        /// </summary>
        /// <param name="command">The command.</param>
        internal static void Import(CommandArgs command)
        {
            Logger.Write("Import start : " + command.SolutionFilePath);
            Guid jobId = CheckForExistingImportJob();
            if (jobId == Guid.Empty)
            {
                ConsoleHelper.Write("Importing solution file to CRM ...", ConsoleColor.Cyan);
                jobId = Guid.NewGuid();
                var solutionFileBytes = File.ReadAllBytes(command.SolutionFilePath);
                var importSolutionRequest = new ImportSolutionRequest { CustomizationFile = solutionFileBytes, PublishWorkflows = command.PublishWorkflows, ImportJobId = jobId, OverwriteUnmanagedCustomizations = command.OverwriteUnmanagedCustomizations };

                Thread progressChecker = new Thread(() => GetProgress(jobId, command.CrmConnectionString));
                progressChecker.Start();
                var response = (ImportSolutionResponse)OrganizationService.Execute(importSolutionRequest);
                progressChecker.Abort();
                Console.WriteLine();
                ConsoleHelper.Write("Importing solution file to CRM ...", ConsoleColor.Cyan);
                ConsoleHelper.Write("Done !", ConsoleColor.Green);
                Console.WriteLine();
            }
            else
            {
                IsCurrentImportRunning = true;
                ConsoleHelper.Write("Retrieving current solution import status ...", ConsoleColor.Cyan);
                Thread progressChecker = new Thread(() => GetProgress(jobId, command.CrmConnectionString));
                progressChecker.Start();
                while (IsCurrentImportRunning)
                {
                    // Wait
                }
                Console.WriteLine();
                ConsoleHelper.Write("Importing solution file to CRM ...", ConsoleColor.Cyan);
                ConsoleHelper.Write("Done !", ConsoleColor.Green);
                Console.WriteLine();
            }

            // Save log file
            ConsoleHelper.Write("Saving solution import log file ...", ConsoleColor.Cyan);
            var importLogRequest = new RetrieveFormattedImportJobResultsRequest { ImportJobId = jobId };
            var importLogResponse = (RetrieveFormattedImportJobResultsResponse)OrganizationService.Execute(importLogRequest);
            File.WriteAllText(command.LogFilePath, importLogResponse.FormattedResults);
            ConsoleHelper.Write("Done !", ConsoleColor.Green);
            Console.WriteLine();
            Logger.Write("Import stop");

            Console.WriteLine();
        }
        #endregion

        #region Check For Existing Import Job
        /// <summary>
        /// Checks for existing import job.
        /// </summary>
        private static Guid CheckForExistingImportJob()
        {
            Logger.Write("CheckForExistingImportJob start");
            Guid jobId = Guid.Empty;
            ConsoleHelper.Write("Check for existing import job ...", ConsoleColor.Gray);
            FilterExpression filter = new FilterExpression();
            filter.AddCondition(new ConditionExpression("completedon", ConditionOperator.Null));
            var query = new QueryExpression("importjob") { ColumnSet = new ColumnSet(true), Criteria = filter };

            var response = OrganizationService.RetrieveMultiple(query);
            if (response.Entities.Count == 1)
            {
                jobId = response.Entities.FirstOrDefault().Id;
            }

            ConsoleHelper.Write("Done !", ConsoleColor.Green);
            Console.WriteLine();
            Logger.Write(string.Format("CheckForExistingImportJob stop : job id = {0}", jobId));
            return jobId;
        }
        #endregion


        #region Get Progress
        /// <summary>
        /// Gets the progress.
        /// </summary>
        /// <param name="importId">The import id.</param>
        /// <param name="crmConnectionString"></param>
        private static void GetProgress(Guid importId, string crmConnectionString)
        {
            if (HasFailed)
            {
                IsCurrentImportRunning = false;
                return;
            }
            try
            {
                var connection = CrmConnection.Parse(crmConnectionString);
                connection.Timeout = new TimeSpan(1, 0, 0);
                IOrganizationService service = new OrganizationService(connection);
                var job = service.Retrieve("importjob", importId, new ColumnSet(true));
                decimal progress = Convert.ToDecimal(job["progress"]);
                ConsoleHelper.RenderConsoleProgress((int)progress, string.Format("Importing solution ... ({0}%)", progress.ToString("0.00")));

                if (job.Attributes.Contains("completedon"))
                {
                    IsCurrentImportRunning = false;
                    return;
                }

                if (progress == 100)
                {
                    IsCurrentImportRunning = false;
                    return;
                }
                IsCurrentImportRunning = true;
            }
            catch (Exception)
            {
                // Ignore
            }

            Thread.Sleep(1000);
            GetProgress(importId, crmConnectionString);
        }
        #endregion

        #region Publish
        /// <summary>
        /// Publishes solution with the specified command.
        /// </summary>
        /// <param name="command">The command.</param>
        internal static void Publish(CommandArgs command)
        {
            Logger.Write("Publish start");
            ConsoleHelper.Write("Publishing ...", ConsoleColor.Gray);
            var publishRequest = new PublishAllXmlRequest();
            OrganizationService.Execute(publishRequest);
            ConsoleHelper.Write("Done !", ConsoleColor.Green);
            Console.WriteLine();
            Logger.Write("Publish stop");
        }
        #endregion
    }
}
